﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Runtime.Serialization;
using System;
using System.Xml.Serialization;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;
using System.Threading;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class CacheBase
    {
        [NonSerialized, XmlIgnore]
        protected Lockable _repopLock;

        [NonSerialized, XmlIgnore]
        protected bool _dirty;

        [NonSerialized, XmlIgnore]
        protected object _defaultPopulateParam;

        [NonSerialized, XmlIgnore]
        protected object[] _storedArgs;

        /// <summary>
        /// True if the cache object has been marked as dirty, otherwise false
        /// </summary>
        public bool Dirty { get { return _dirty; } set { _dirty = value; } }

        /// <summary>
        /// The default parameter to pass in the cache population function
        /// </summary>
        public object DefaultPopulateParam { get { return _defaultPopulateParam; } set { _defaultPopulateParam = value; } }

        /// <summary>
        /// Storage for other arguments passed into the cache population function
        /// </summary>
        public object[] StoredArgs { get { return _storedArgs; } set { _storedArgs = value; } }

        /// <summary>
        /// Marks the cache as dirty
        /// </summary>
        public virtual void MarkDirty() { _dirty = true; }

        /// <summary>
        /// Method info used to populate the cache
        /// </summary>
        private MethodInfo PopulateCacheMethodInfo { get; set; }
    }

    public static class CacheCollectionsDirty
    {
        public static ThreadSafeLookup<Type, List<PropertyInfo>> _cachedPropertyCache = new ThreadSafeLookup<Type, List<PropertyInfo>>();

        private static List<PropertyInfo> GetPropertiesForType(Type t)
        {
            List<PropertyInfo> props = _cachedPropertyCache[t];

            if (props == null)
            {
                props = new List<PropertyInfo>();
                PropertyInfo[] propinfos = t.GetProperties();

                foreach (PropertyInfo prop in propinfos)
                {
                    object[] os = prop.GetCustomAttributes(typeof(IsCached), true);
                    if (os != null && os.Length > 0)
                    {
                        if ((os[0] as IsCached).Cached)
                        {
                            props.Add(prop);
                        }
                    }
                }

                _cachedPropertyCache.Add(t, props);
            }

            return props;
        }

        /// <summary>
        /// Marks all cached properties on each enumerable item as dirty/invalid
        /// </summary>
        public static int DirtyCachedProperties<T>(this IEnumerable<T> items) where T : class
        {
            int count = 0;

            foreach (T item in items)
            {
                if (item != null)
                {
                    count += item.DirtyCachedProperties<T>();
                }
            }

            return count;
        }

        /// <summary>
        /// Marks all cached properties on list entry as dirty/invalid
        /// </summary>
        public static int DirtyCachedProperties<T>(this List<T> items) where T : class
        {
            return DirtyCachedProperties<T>((IEnumerable<T>)items);
        }

        /// <summary>
        /// Marks all properties marked with the IsCached attribute on item as dirty
        /// </summary>
        public static int DirtyCachedProperties<T>(this T item) where T : class
        {
            int count = 0;
            List<PropertyInfo> props = GetPropertiesForType(typeof(T));

            for (int n = 0; n < props.Count; n++)
            {
                PropertyInfo prop = props[n];
                object o = prop.GetValue(item, null);
                if (o != null)
                {
                    CacheBase cache = o as CacheBase;
                    if (cache != null)
                    {
                        count++;
                        cache.MarkDirty();
                    }
                    else
                    {
                        throw new Exception("Object " + o.ToString() + ", value of property " + prop.Name + ", does not inherit from CacheBase");
                    }
                }
            }

            return count;
        }
    }

    /// <summary>
    /// Caches an object or value of type T
    /// </summary>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class Cached<T> : GenericCacheBase<T>, ICacheBase<T>
    {
        public Cached(TPopulateCacheDelegate<T> populateCacheDelegate) : base(populateCacheDelegate) { }
        public Cached(TPopulateCacheDelegateDirect<T> populateCacheDelegate) : base(populateCacheDelegate) { }
        public Cached() { }

        /// <summary>
        /// Retrieves the cached item, possibly populating the cache if it is dirty
        /// or has not been previously populated
        /// </summary>
        public override T GetItem(object populateCacheParameter, params object[] args)
        {
            if (IsCacheValid(populateCacheParameter))
            {
                // If cached item is valid, return the cached item
                return _item;
            }
            else
            {
                // otherwise, populate the cache
                if (_populateCacheDelegate != null || _populateCacheDelegateDirect != null)
                {
                    if (_repopLock == null)
                    {
                        Interlocked.CompareExchange<Lockable>(ref _repopLock, new Lockable(), null);
                    }

                    while (_repopLock.Locked) { Thread.Sleep(10); }

                    // lock so that only one thread can populate the cache at any given time
                    _repopLock.AquireLock();
                    {
                        try
                        {
                            if (!IsCacheValid(populateCacheParameter))
                            {
                                T outval;

                                // Call the populate cache delegate
                                if (_populateCacheDelegateDirect != null)
                                {
                                    Item = _populateCacheDelegateDirect(populateCacheParameter);
                                    return _item;
                                }
                                else if (_populateCacheDelegate(populateCacheParameter, out outval, args))
                                {
                                    Item = outval;
                                    return _item;
                                }
                            }
                        }
                        finally
                        {
                            _repopLock.ReleaseLock();
                        }
                    }
                }
                return default(T);
            }
        }
    }

#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class CachedByKey<TKey, T> : GenericCacheAbstractBase<T>, ICacheBase<T> where T : class, IKeyedOnProperty<TKey>
    {
        public CachedByKey() { }
        public CachedByKey(TPopulateCacheDelegate<T> populateCacheDelegate) { SetPopulateCacheDelegate(populateCacheDelegate); }

        [NonSerialized, XmlIgnore]
        protected ThreadSafeLookupOnAutoKey<TKey, T> _item;

        public override bool IsCacheValid() { return (_item != null && !_dirty); }
        public override bool IsCacheValid(object populateCacheParameter) { return (!_dirty && _item != null && _item[(TKey)populateCacheParameter] != null); }

        public virtual T GetItem(TKey populateCacheParameter, params object[] args)
        {
            return GetItem(populateCacheParameter, args);
        }

        public override T GetItem(object populateCacheParameter, params object[] args)
        {
            T item = null;

            if (IsCacheValid(populateCacheParameter))
            {
                item = _item[(TKey)populateCacheParameter];
            }

            if (item == null)
            {
                if (_populateCacheDelegate != null)
                {
                    if (_repopLock == null)
                    {
                        Interlocked.CompareExchange<Lockable>(ref _repopLock, new Lockable(), null);
                    }

                    while (_repopLock.Locked) { Thread.Sleep(10); }

                    _repopLock.AquireLock();
                    {
                        try
                        {
                            if (_dirty)
                            {
                                Interlocked.Exchange<ThreadSafeLookupOnAutoKey<TKey, T>>(ref _item, null);
                                _dirty = false;
                            }

                            if (!IsCacheValid((TKey)populateCacheParameter))
                            {
                                T outval;

                                if (_populateCacheDelegate(populateCacheParameter, out outval, args))
                                {
                                    if (_item == null)
                                    {
                                        Interlocked.Exchange<ThreadSafeLookupOnAutoKey<TKey, T>>(ref _item, new ThreadSafeLookupOnAutoKey<TKey, T>());
                                    }

                                    _item.AddOrSet((TKey)populateCacheParameter, outval);
                                    return outval;
                                }
                            }
                        }
                        finally
                        {
                            _repopLock.ReleaseLock();
                        }
                    }
                }
            }
            return item;
        }

        public override T GetItem()
        {
            return GetItem(_defaultPopulateParam, _storedArgs);
        }
    }

#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class CachedByManualKey<TKey, T> : GenericCacheAbstractBase<T>, ICacheBase<T> where T : class
    {
        public CachedByManualKey() { }
        public CachedByManualKey(TPopulateCacheDelegate<T> populateCacheDelegate) { SetPopulateCacheDelegate(populateCacheDelegate); }

        [NonSerialized, XmlIgnore]
        protected ThreadSafeLookup<TKey, T> _cachedItemsByKey;

        public override bool IsCacheValid() { return (_cachedItemsByKey != null && !_dirty); }
        public override bool IsCacheValid(object populateCacheParameter) { return (!_dirty && _cachedItemsByKey != null && _cachedItemsByKey[(TKey)populateCacheParameter] != null); }

        public virtual T GetItem(TKey key, params object[] args)
        {
            return GetItem((object)key, args);
        }

        /// <summary>
        /// populateCacheParameter is the key for the cached item
        /// </summary>
        /// <param name="populateCacheParameter"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public override T GetItem(object populateCacheParameter, params object[] args)
        {
            T item = null;

            if (IsCacheValid(populateCacheParameter))
            {
                item = _cachedItemsByKey[(TKey)populateCacheParameter];
            }

            if (item == null)
            {
                if (_populateCacheDelegate != null)
                {
                    if (_repopLock == null)
                    {
                        Interlocked.CompareExchange<Lockable>(ref _repopLock, new Lockable(), null);
                    }

                    while (_repopLock.Locked) { Thread.Sleep(10); }

                    _repopLock.AquireLock();
                    {
                        try
                        {
                            if (_dirty)
                            {
                                Interlocked.Exchange<ThreadSafeLookup<TKey, T>>(ref _cachedItemsByKey, null);
                                _dirty = false;
                            }

                            if (!IsCacheValid((TKey)populateCacheParameter))
                            {
                                T outval;
                                if (_populateCacheDelegate(populateCacheParameter, out outval, args))
                                {
                                    if (_cachedItemsByKey == null)
                                    {
                                        Interlocked.Exchange<ThreadSafeLookup<TKey, T>>(ref _cachedItemsByKey, new ThreadSafeLookup<TKey, T>());
                                    }
                                    _cachedItemsByKey.AddOrSet((TKey)populateCacheParameter, outval);
                                    return outval;
                                }
                            }
                        }
                        finally
                        {
                            _repopLock.ReleaseLock();
                        }
                    }
                }
            }
            return item;
        }

        public override T GetItem()
        {
            return GetItem(_defaultPopulateParam, _storedArgs);
        }
    }

#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class CachedListByKey<TKey, TList, T> : GenericCacheAbstractBase<TList>, ICacheBase<TList>
        where T : class
        where TList : List<T>
    {
        public CachedListByKey(TPopulateCacheDelegate<TList> populateCacheDelegate) : base(populateCacheDelegate) { }
        public CachedListByKey(TPopulateCacheDelegateDirect<TList> populateCacheDelegate) : base(populateCacheDelegate) { }
        public CachedListByKey() { }

        [NonSerialized, XmlIgnore]
        protected ThreadSafeLookup<TKey, TList> _item;

        public override bool IsCacheValid() { return (_item != null && !_dirty); }
        public override bool IsCacheValid(object populateCacheParameter) { return (!_dirty && _item != null && _item[(TKey)populateCacheParameter] != null); }

        public virtual TList GetItem(TKey populateCacheParameter, params object[] args)
        {
            return GetItem((object)populateCacheParameter, args);
        }

        public override TList GetItem(object populateCacheParameter, params object[] args)
        {
            TList item = null;

            if (IsCacheValid(populateCacheParameter))
            {
                item = _item[(TKey)populateCacheParameter];
            }

            if (item == null)
            {
                if (_populateCacheDelegate != null)
                {
                    Interlocked.CompareExchange<Lockable>(ref _repopLock, new Lockable(), null);
                    while (_repopLock.Locked) { Thread.Sleep(50); }
                    _repopLock.AquireLock();
                    {
                        try
                        {
                            if (_dirty)
                            {
                                Interlocked.Exchange<ThreadSafeLookup<TKey, TList>>(ref _item, null);
                                _dirty = false;
                            }

                            if (!IsCacheValid((TKey)populateCacheParameter))
                            {
                                TList outval;
                                if (_populateCacheDelegate(populateCacheParameter, out outval, args))
                                {
                                    if (_item == null) Interlocked.Exchange<ThreadSafeLookup<TKey, TList>>(ref _item, new ThreadSafeLookup<TKey, TList>());
                                    _item.AddOrSet((TKey)populateCacheParameter, (TList)new List<T>(outval));
                                    return outval;
                                }
                            }
                        }
                        finally
                        {
                            _repopLock.ReleaseLock();
                        }
                    }
                }
            }
            return item;
        }

        public override TList GetItem()
        {
            return GetItem(_defaultPopulateParam, _storedArgs);
        }
    }

    [Serializable]
    [DataContract]
    public class CachedObject
    {
        [DataMember]
        public object Obj { get; set; }
        [DataMember]
        public bool Dirty { get; set; }
    }

    public static class CacheExtensions
    {
        public static void SafeMarkDirty<T>(this Cached<T> cached)
        {
            if (cached != null)
                cached.MarkDirty();
        }
    }

#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public abstract class GenericCacheAbstractBase<T> : CacheBase, ICacheBase<T>
    {
        //[NonSerialized, XmlIgnore]
        public TPopulateCacheDelegate<T> _populateCacheDelegate;

        //[NonSerialized, XmlIgnore]
        public TPopulateCacheDelegateDirect<T> _populateCacheDelegateDirect;

        public GenericCacheAbstractBase(TPopulateCacheDelegate<T> populateCacheDelegate) { SetPopulateCacheDelegate(populateCacheDelegate); }
        public GenericCacheAbstractBase(TPopulateCacheDelegateDirect<T> populateCacheDelegate) { SetPopulateCacheDelegate(populateCacheDelegate); }
        public GenericCacheAbstractBase() { }

        public abstract bool IsCacheValid(object populateCacheParameter);
        public abstract bool IsCacheValid();

        /// <summary>
        /// Sets the delegate used to populate the cache
        /// </summary>
        /// <param name="populateCacheDelegate"></param>
        public virtual void SetPopulateCacheDelegate(TPopulateCacheDelegate<T> populateCacheDelegate)
        {
            if (_populateCacheDelegate == null)
            {
                Interlocked.CompareExchange<TPopulateCacheDelegate<T>>(ref _populateCacheDelegate, populateCacheDelegate, null);
            }
        }

        /// <summary>
        /// Sets the delegate used to populate the cache
        /// </summary>
        /// <param name="populateCacheDelegate"></param>
        public virtual void SetPopulateCacheDelegate(TPopulateCacheDelegateDirect<T> populateCacheDelegate)
        {
            if (_populateCacheDelegateDirect == null)
            {
                Interlocked.CompareExchange<TPopulateCacheDelegateDirect<T>>(ref _populateCacheDelegateDirect, populateCacheDelegate, null);
            }
        }

        /// <summary>
        /// Retrieves the cached item, given a populateCache parameter and other arguments
        /// </summary>
        public abstract T GetItem(object populateCacheParameter, params object[] args);

        /// <summary>
        /// Retrieves the cached object
        /// </summary>
        public virtual T GetItem()
        {
            return GetItem(_defaultPopulateParam, _storedArgs);
        }
    }

    /// <summary>
    /// Generic Cache class, used to cache a single item
    /// </summary>
    /// <typeparam name="T"></typeparam>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public abstract class GenericCacheBase<T> : GenericCacheAbstractBase<T>, ICacheBase<T>
    {
        [NonSerialized, XmlIgnore]
        protected T _item;

        public GenericCacheBase(TPopulateCacheDelegate<T> populateCacheDelegate) : base(populateCacheDelegate) { }
        public GenericCacheBase(TPopulateCacheDelegateDirect<T> populateCacheDelegate) : base(populateCacheDelegate) { }
        public GenericCacheBase() { }

        /// <summary>
        /// Determines if the cached object is valid/unchanged or invalid/dirty/not retrieved
        /// </summary>	
        public override bool IsCacheValid() { return (_item != null && !_dirty); }

        /// <summary>
        /// Determines if the cached object is valid/unchanged or invalid/dirty/not retrieved, with an optional cache population parameter
        /// </summary>
        public override bool IsCacheValid(object populateCacheParameter) { return (_item != null && !_dirty); }

        /// <summary>
        /// The cached item
        /// </summary>
        public virtual T Item
        {
            get { return (_dirty ? default(T) : _item); }
            set { _item = value; _dirty = false; }
        }
    }

    /// <summary>
    /// Provides a global class to cache any object
    /// Care must be taken not to have code that accidentally uses the same cache object keys
    /// </summary>
    public static class StaticCache
    {
        public static ThreadSafeLookup<string, CachedObject> _cachedObjects = new ThreadSafeLookup<string, CachedObject>();

        /// <summary>
        /// Marks a cache object dirty (by key)
        /// </summary>
        /// <param name="key"></param>
        public static void MarkDirty(string key)
        {
            CachedObject c = _cachedObjects[key];

            if (c != null)
            {
                c.Dirty = true;
            }
        }

        /// <summary>
        /// Gets the value of an object cached by string key.
        /// If the object is not in cache or is dirty, runs populateCacheDelegate to get its value and repopulate the cache for the object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="populateCacheDelegate"></param>
        /// <returns></returns>
        public static T GetValue<T>(string key, Func<T> populateCacheDelegate)
        {
            CachedObject c = _cachedObjects[key];
            if (c == null)
            {
                c = new CachedObject();
                c.Obj = populateCacheDelegate();
                _cachedObjects.AddOrSet(key, c);
                return (T)c.Obj;
            }
            else if (c.Dirty)
            {
                c.Obj = populateCacheDelegate();
                c.Dirty = false;
                return (T)c.Obj;
            }
            else
            {
                return (T)c.Obj;
            }
        }
    }

}
